{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "66f09374-d47a-440a-919a-33eeb87cf4da",
   "metadata": {},
   "outputs": [],
   "source": [
    "from gs_quant.session import Environment\n",
    "from gs_quant.instrument import OptionStyle\n",
    "from gs_quant.backtests.strategy import Strategy\n",
    "from gs_quant.backtests.triggers import *\n",
    "from gs_quant.backtests.actions import *\n",
    "from gs_quant.backtests.equity_vol_engine import *\n",
    "from gs_quant.backtests.generic_engine import GenericEngine\n",
    "from gs_quant.target.measures import Price\n",
    "from datetime import datetime, date\n",
    "\n",
    "import gs_quant.risk as risk"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4711538f-454f-43a4-ab94-687d39782579",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Initialize session\n",
    "from gs_quant.session import GsSession\n",
    "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',)) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eb25d59a-8c1d-4e53-a92f-e28937ca34e6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define backtest dates\n",
    "start_date = date(2022, 1, 1)\n",
    "end_date = date(2022, 2, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "09e44a2e-fdb5-4781-b65a-aa4b968948f5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define instruments for strategy\n",
    "# Portfolio of two eq options\n",
    "spx_opt = EqOption('.SPX', expiration_date='2m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, name='spx')\n",
    "ndx_opt = EqOption('.NDX', expiration_date='2m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, name='ndx')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f6068e9e-e104-4816-ab42-609ed3be3adf",
   "metadata": {},
   "source": [
    "## RebalanceAction rebalances a trade's quantity according to a custom function"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12c81ab7-d0ab-462f-be30-fd1f45edf12a",
   "metadata": {},
   "source": [
    "#### This function returns the no. of .NDX options held"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "95e58596-1fdd-4e35-b057-49193b180830",
   "metadata": {},
   "outputs": [],
   "source": [
    "def match_ndx_holding(state, backtest, info):\n",
    "    port = backtest.portfolio_dict\n",
    "    current_ndx_notional = sum([x.number_of_options for x in port[state].all_instruments if isinstance(x, EqOption) and x.underlier == '.NDX'])\n",
    "\n",
    "    return current_ndx_notional"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9fdebe41-933b-4ee5-a6cf-d0b3cac8ff79",
   "metadata": {},
   "source": [
    "#### AddTradeAction adds .NDX options daily, RebalanceAction rebalances the .SPX option according to the total number of .NDX options held weekly"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4fdd6cdd-7368-4c68-bb25-3c1fcc112e40",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Add NDX options daily\n",
    "add_trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b')\n",
    "add_ndx = AddTradeAction(ndx_opt, '2m', name='Action1')\n",
    "add_trigger = PeriodicTrigger(add_trig_req, add_ndx)\n",
    "\n",
    "# Rebalance SPX option holdings weekly\n",
    "rebalance_trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1w')\n",
    "# Need to resolve an option before rebalancing it. In this case we want to rebalance the option we are starting with\n",
    "with PricingContext(start_date):\n",
    "    spx_opt.resolve()\n",
    "rebalance_spx = RebalanceAction(spx_opt, 'number_of_options', match_ndx_holding)\n",
    "rebalance_trigger = PeriodicTrigger(rebalance_trig_req, rebalance_spx)\n",
    "\n",
    "strategy = Strategy(spx_opt, [add_trigger, rebalance_trigger])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c03ab015-0f64-4ef0-b2b7-0c69381e8e7d",
   "metadata": {},
   "source": [
    "#### Run the strategy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1005273b-7d8e-4170-9ba0-42317b0deb5d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# run backtest daily\n",
    "GE = GenericEngine()\n",
    "backtest = GE.run_backtest(strategy, start=start_date, risks=[Price], end=end_date, frequency='1b',\n",
    "                           show_progress=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "892047e1-7813-4b87-afe0-9f192f1f03c2",
   "metadata": {},
   "source": [
    "#### See total no. of options by underlier"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "376e40c5-5341-4ebf-b304-86b1b2a7d2ef",
   "metadata": {},
   "outputs": [],
   "source": [
    "ts = backtest.strategy_as_time_series()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a3ca6a7e-87b7-44cd-ad97-6ca4b30ffb42",
   "metadata": {},
   "outputs": [],
   "source": [
    "ndx = ts[~ts.index.get_level_values('Instrument Name').str.contains('spx')]\n",
    "ndx = ndx.groupby('Pricing Date').agg({('Static Instrument Data', 'number_of_options'): ['sum']})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "de82e4d0-7086-4c8f-a8c2-fef3803ad52e",
   "metadata": {},
   "outputs": [],
   "source": [
    "spx = ts[~ts.index.get_level_values('Instrument Name').str.contains('ndx')]\n",
    "spx = spx.groupby('Pricing Date').agg({('Static Instrument Data', 'number_of_options'): ['sum']})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "54e4bd65-3f5b-439e-aad6-6e2c8b397e02",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "plt.plot(ndx['Static Instrument Data']['number_of_options']['sum'],label='NDX')\n",
    "plt.plot(spx['Static Instrument Data']['number_of_options']['sum'],label='SPX')\n",
    "\n",
    "plt.legend(prop={'size': 15})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5b32a18f-6377-43c2-9868-eedb3a40e8af",
   "metadata": {},
   "outputs": [],
   "source": [
    "# View Mark to Market\n",
    "pd.DataFrame({'Generic backtester': backtest.result_summary[Price]}).plot(figsize=(10, 6), title='Mark to market')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "578c4bf6-f080-4e34-ab1d-ac2a6831d5ab",
   "metadata": {},
   "outputs": [],
   "source": [
    "# View Performance\n",
    "pd.DataFrame({'Generic backtester': backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price]}).plot(figsize=(10, 6), title='Performance')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
